home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 8: LINUX Games / Linux Cubed Series 8 - LINUX Games.iso / games / x11 / rpg / crossfir.92 / crossfir / crossfire-0.92.5 / server / skill_util.c < prev    next >
C/C++ Source or Header  |  1996-07-24  |  48KB  |  1,516 lines

  1. /* Created July 95 to separate skill utilities from actual skills -b.t. */
  2.  
  3. /* Reconfigured skills code to allow linking of skills to experience
  4.  * categories. This is done solely through the init_new_exp_system() fctn.
  5.  * June/July 1995 -b.t. thomas@astro.psu.edu
  6.  */
  7.  
  8. /* July 1995 - Initial associated skills coding. Experience gains
  9.  * come solely from the use of skills. Overcoming an opponent (in combat,
  10.  * finding/disarming a trap, stealing from somebeing, etc) gains
  11.  * experience. Calc_skill_exp() handles the gained experience using
  12.  * modifications in the skills[] table. - b.t.
  13.  */
  14.  
  15. /* define the following for skills utility debuging */
  16. /* #define SKILL_UTIL_DEBUG */
  17.  
  18. #include <global.h>
  19. #include <object.h>
  20. #ifndef __CEXTRACT__
  21. #include <sproto.h>
  22. #endif
  23. #include <living.h>     /* for defs of STR,CON,DEX,etc. -b.t.*/
  24. #include <skills.h>
  25. #include <skillist.h>
  26. #include <spells.h>
  27.  
  28. /* table for stat modification of exp */
  29.  
  30. float stat_exp_mult[MAX_STAT+1]={
  31.  0.0,
  32.  0.00, 0.01, 0.03, 0.05,        /* 1 - 4 */ 
  33.  0.20, 0.35, 0.5,            /* 5 - 7 */
  34.  0.65, 0.7, 0.75, 0.8,            /* 8 - 11 */ 
  35.  0.85, 0.9, 0.95,            /* 12 - 14 */ 
  36.  1.0, 1.05, 1.10, 1.15, 1.20,        /* 15 - 19 */ 
  37.  1.3, 1.4, 1.5, 1.6, 1.7,        /* 20 - 24 */ 
  38.  2.0, 2.2, 2.4, 2.6, 2.8, 3.0        /* 25 - 30 */ 
  39. };
  40.  
  41.  
  42. /* do_skill() - Main skills use function-similar in scope to cast_spell(). 
  43.  * We handle all requests for skill use outside of some combat here. 
  44.  * We require a separate routine outside of fire() so as to allow monsters 
  45.  * to utilize skills. Success should be the amount of experience gained 
  46.  * from the activity. Also, any skills that monster will use, will need
  47.  * to return a positive value of success if performed correctly. Usually
  48.  * this is handled in calc_skill_exp(). Thus failed skill use re-
  49.  * sults in a 0, or false value of 'success'.
  50.  *  - b.t.  thomas@astro.psu.edu
  51.  */
  52.  
  53. int do_skill (object *op, int dir, char *params) {
  54.   int success=0;        /* needed for monster_skill_use() too */
  55.   int skill = op->chosen_skill->stats.sp;
  56.  
  57.     switch(skill) {
  58.       case SK_STEALING:
  59.         success = steal(op, dir);
  60.         break;
  61.       case SK_LOCKPICKING:
  62.         success = pick_lock(op, dir);
  63.         break;
  64.       case SK_HIDING:
  65.         success = hide(op);
  66.         break;
  67.       case SK_JUMPING:
  68.         success = jump(op, dir);
  69.         break;
  70.       case SK_INSCRIPTION:
  71.         success = write_on_item(op,params);
  72.         break;
  73.       case SK_MEDITATION:
  74.         meditate(op);
  75.         break;
  76.     /* note that the following 'attack' skills gain exp through hit_player() */
  77.       case SK_KARATE:
  78.         (void) attack_hth(op,dir,"karate-chopped");
  79.         break;
  80.       case SK_BOXING:
  81.         (void) attack_hth(op,dir,"punched");
  82.         break;
  83.       case SK_FLAME_TOUCH:
  84.         (void) attack_hth(op,dir,"flamed");
  85.         break;
  86.       case SK_CLAWING:
  87.         (void) attack_hth(op,dir,"clawed");
  88.         break;
  89.       case SK_MELEE_WEAPON:
  90.         (void) attack_melee_weapon(op,dir,NULL);
  91.         break;
  92.       case SK_FIND_TRAPS:
  93.         success = find_traps(op);
  94.         break;
  95.       case SK_MUSIC:
  96.         success = singing(op,dir);
  97.         break;
  98.       case SK_ORATORY:
  99.         success = use_oratory(op,dir);
  100.         break;
  101.       case SK_SMITH:
  102.       case SK_BOWYER:
  103.       case SK_JEWELER:
  104.       case SK_ALCHEMY:
  105.       case SK_THAUMATURGY:
  106.       case SK_LITERACY:
  107.       case SK_DET_MAGIC:
  108.       case SK_DET_CURSE:
  109.       case SK_WOODSMAN:
  110.         success = skill_ident(op);
  111.         break;
  112.       case SK_REMOVE_TRAP:
  113.         success = remove_trap(op,dir);
  114.         break;
  115.       case SK_SET_TRAP:
  116.       case SK_THROWING:
  117.            new_draw_info(NDI_UNIQUE, 0,op,"This skill is not currently implemented.");
  118.         break;
  119.       case SK_USE_MAGIC_ITEM:
  120.       case SK_MISSILE_WEAPON:
  121.            new_draw_info(NDI_UNIQUE, 0,op,"There is no special attack for this skill.");
  122.         break;
  123.       case SK_PRAYING:
  124.     success = pray(op);
  125.     break;
  126.       case SK_SPELL_CASTING:
  127.       case SK_CLIMBING:
  128.       case SK_BARGAINING:
  129.            new_draw_info(NDI_UNIQUE, 0,op,"This skill is already in effect.");
  130.         break;
  131.       default:
  132.         LOG(llevDebug,"%s attempted to use unknown skill: %d\n"
  133.                 ,query_name(op), op->chosen_skill->stats.sp);
  134.         break;
  135.     }
  136.  
  137.    /* For players we now update the speed_left from using the skill. 
  138.     * Monsters have no skill use time because of the random nature in 
  139.     * which use_monster_skill is called already simulates this. -b.t.
  140.     */
  141.  
  142.     if(op->type==PLAYER) op->speed_left -= get_skill_time(op,skill);
  143.  
  144.    /* this is a good place to add experience for successfull use of skills.
  145.     * Note that add_exp() will figure out player/monster experience
  146.     * gain problems.
  147.     */
  148.  
  149.     if(success&&skills[skill].category!=EXP_NONE) add_exp(op,success);
  150.             
  151.     return success;
  152. }
  153.  
  154. /* calc_skill_exp() - calculates amount of experience can be gained for
  155.  * successfull use of a skill.  Returns value of experience gain.
  156.  * Here we take the view that a player must 'overcome an opponent'
  157.  * in order to gain experience. Examples include foes killed combat,
  158.  * finding/disarming a trap, stealing from somebeing, etc.
  159.  * The gained experience is based primarily on the difference in levels,
  160.  * exp point value of vanquished foe, the relevent stats of the skill being
  161.  * used and modifications in the skills[] table.
  162.  *
  163.  * For now, monsters and players will be treated differently. Below I give
  164.  * the algorithm for *PLAYER* experience gain. Monster exp gain is simpler.
  165.  * Monsters just get 10% of the exp of the opponent.
  166.  *
  167.  *  Example: the basic exp gain for player 'who' how "vanquished" oppoenent
  168.  * 'op' using skill 'sk' is:
  169.  *
  170.  *      EXP GAIN = (op->exp + skills[sk].bexp) * lvl_mult * stat_mult
  171.  *
  172.  *  where lvl_mult is
  173.  *
  174.  *  for(pl->level < op->level):: 
  175.  *    lvl_mult  = skills[sk].lexp * (op->level - pl->level) 
  176.  *  for(pl->level = op->level):: 
  177.  *    lvl_mult  = skills[sk].lexp; 
  178.  *  for(pl->level > op->level):: 
  179.  *    lvl_mult = op_lvl/pl_lvl;
  180.  *
  181.  *  and stat_mult is taken from stat_exp_mult[] table above.
  182.  *
  183.  * Coded by b.t. thomas@astro.psu.edu
  184.  */
  185.  
  186. int calc_skill_exp(object *who, object *op) {
  187.   int who_lvl= SK_level(who);
  188.   int sk,op_exp=0,op_lvl= 0;
  189.   float base,value,stat_mult=1.0,lvl_mult=0.0;
  190.  
  191.     /* sometimes we dont have a specific opponent to 'overcome'.
  192.      * 'hiding' is just such a case. For these skills, our
  193.      * 'opponent is the map we are on. Thus experience
  194.      * gain is just skills[sk].bexp, and modified by the
  195.      * map difficulty instead of an opponent SK_level. 
  196.      * In the case that map difficulty =< player then op_lvl = 0
  197.      */
  198.  
  199.     /* Oct 95 - where we have an object, I expanded our treatment
  200.      * to 3 cases:
  201.      * non-living magic obj, runes and everything else.
  202.      *
  203.      * If an object is not alive and magical we set the base exp higher to
  204.      * help out exp awards for skill_ident skills. Also, if
  205.      * an item is type RUNE, we give out exp based on stats.Cha
  206.      * and level (this was the old system) -b.t. 
  207.      */ 
  208.  
  209.     if(!op) {         /* no item/creature */ 
  210.         op_lvl= who->map->difficulty < 1 ? 1: who->map->difficulty;
  211.         op_exp = 0;
  212.     } else if(op->type==RUNE) { /* all traps. If stats.Cha > 1 we use that
  213.                  * for the amount of experience */
  214.         op_exp = op->stats.Cha>1 ? op->stats.Cha : op->stats.exp;
  215.         op_lvl = op->level;
  216.     } else {         /* all other items/living creatures */ 
  217.         op_exp = op->stats.exp;
  218.         if(!QUERY_FLAG(op,FLAG_ALIVE)) { /* for ident/make items */ 
  219.        op_lvl = op->level;
  220.        op_lvl += op->magic ? 0 : (5 * op->magic);
  221.     } else /* its a monster, grab its level */
  222.        op_lvl= SK_level(op);
  223.     }
  224.  
  225.     if(op_lvl<1) op_lvl = 1;
  226.  
  227.     if(who->type!=PLAYER) {             /* for monsters only */
  228.         return ((int) (op_exp*0.1)+1);    /* we add one to insure positive value is returned */
  229.     } else {                            /* for players */
  230.       /* FIRST, what skill are we using? */
  231.       if(who->chosen_skill==NULL) {
  232.           LOG(llevError,"Bad call of calc_skill_exp() by player. \n");
  233.           return 0;
  234.       } else
  235.           sk = who->chosen_skill->stats.sp;
  236.  
  237.       base = op_exp + skills[sk].bexp;  /* get base exp */
  238.  
  239.  
  240.   /* get level multiplier */
  241.       if(who_lvl < op_lvl)        
  242.           lvl_mult=(float) skills[sk].lexp * (float) ((float) op_lvl - (float) who_lvl);
  243.       else if(who_lvl == op_lvl)        
  244.           lvl_mult = (float) skills[sk].lexp;
  245.       else 
  246.           lvl_mult = ((float) ((float) op_lvl)/((float) who_lvl));
  247.  
  248.       stat_mult = calc_stat_mult(who,sk);
  249.     }
  250.  
  251.     /* assemble the exp total, and return value */
  252.  
  253.     value =  base * (lvl_mult * stat_mult);
  254.  
  255. #ifdef SKILL_UTIL_DEBUG
  256.       LOG(llevDebug,"calc_skill_exp(): who: %s(lvl:%d)  op:%s(lvl:%d)\n",
  257.         who->name,who_lvl,op->name,op_lvl);
  258.       LOG(llevDebug,"%s(%s): base_exp=%f lvl_mult=%f stat_mult=%f value=%f\n",
  259.                 who->name,skills[sk].name,base,lvl_mult,stat_mult,value);
  260. #endif
  261.     return ( (int) value); 
  262. }
  263.  
  264.  
  265. /* calc_stat_mult() - figures a factor to modify gained experience by
  266.  * based on the sum of relvant stats of skill 'sk' being used by 'who'.
  267.  */
  268.  
  269. float calc_stat_mult(object *who,int sk) {
  270.   int sum;
  271.   float factor = 1.0;
  272.          
  273.     sum = get_weighted_skill_stat_sum (who,sk);
  274.  
  275.     /* look up factor for weighted sum */
  276.     if(sum <MAX_STAT) factor = stat_exp_mult[sum];
  277.  
  278.     return factor;
  279. }
  280.  
  281. /* find relevant stats or a skill then return their weighted sum. 
  282.  * I admit the calculation is done in a retarded way.
  283.  * If stat1==NO_STAT this isnt an associated stat. Returns
  284.  * zero then. -b.t. 
  285.  */  
  286.  
  287. int get_weighted_skill_stat_sum ( object *who, int sk) {
  288.    float sum;
  289.    int number = 1;
  290.  
  291.     if(skills[sk].stat1==NO_STAT) {
  292.           return 0;
  293.     } else
  294.          sum = get_attr_value(&(who->stats),skills[sk].stat1);
  295.  
  296.     if(skills[sk].stat2!=NO_STAT) {
  297.          sum += get_attr_value(&(who->stats),skills[sk].stat2);
  298.          number++;
  299.     }
  300.  
  301.     if(skills[sk].stat3!=NO_STAT) {
  302.          sum += get_attr_value(&(who->stats),skills[sk].stat3);
  303.          number++;
  304.     }
  305.  
  306.     return ((int) sum/number);
  307. }
  308.  
  309. /* init_new_exp_system() - called in init(). This function will reconfigure
  310.  * the properties of skills array and experience categories, and links
  311.  * each skill to the appropriate experience category
  312.  * -b.t. thomas@astro.psu.edu
  313.  */
  314.  
  315. void init_new_exp_system() {
  316.   static int init_new_exp_done = 0;
  317.   int i;
  318.  
  319.   if (init_new_exp_done)
  320.     return;
  321.   init_new_exp_done = 1;
  322.  
  323.   init_exp_obj();     /* locate the experience objects and create list of them */
  324.   link_skills_to_exp(); /* link skills to exp cat, based on shared stats */
  325.   (void) read_skill_params(); /* overide skills[] w/ values from skill_params */ 
  326.  
  327. #ifdef DUMP_SWITCHES
  328.   if (dump_monsters == 5) {
  329.     char buf[MAX_BUF];
  330.  
  331.     fprintf(stderr, "\n");
  332.     fprintf(stderr, "exper_catgry \t str \t dex \t con \t wis \t cha \t int \t pow \n");
  333.     for (i = 0; i < nrofexpcat; i++)
  334.       fprintf(stderr, "%d-%s \t %d \t %d \t %d \t %d \t %d \t %d \t %d \n",
  335.         i,exp_cat[i]->name,
  336.                 exp_cat[i]->stats.Str,exp_cat[i]->stats.Dex,exp_cat[i]->stats.Con,
  337.                 exp_cat[i]->stats.Wis,exp_cat[i]->stats.Cha,exp_cat[i]->stats.Int, 
  338.         exp_cat[i]->stats.Pow);
  339.  
  340.     fprintf(stderr, "\n");
  341.     sprintf(buf,"%20s  %12s  %4s %4s %4s  %5s %5s %5s\n",
  342.     "sk#       Skill name","ExpCat","Time","Base","xlvl","Stat1","Stat2","Stat3");
  343.     fprintf(stderr, buf);
  344.     sprintf(buf,"%20s  %12s  %4s %4s %4s  %5s %5s %5s\n",
  345.     "---       ----------","------","----","----","----","-----","-----","-----");
  346.     fprintf(stderr, buf);
  347.     for (i = 0; i < NROFSKILLS; i++) {
  348.       sprintf(buf,"%2d-%17s  %12s  %4d %4ld %4g  %5s %5s %5s\n", i, skills[i].name,
  349.         exp_cat[skills[i].category]!=NULL?exp_cat[skills[i].category]->name:"NONE",
  350.         skills[i].time,
  351.         skills[i].bexp,
  352.         skills[i].lexp,
  353.         skills[i].stat1!= NO_STAT ? short_stat_name[skills[i].stat1]: "---",
  354.         skills[i].stat2!= NO_STAT ? short_stat_name[skills[i].stat2]: "---",
  355.         skills[i].stat3!= NO_STAT ? short_stat_name[skills[i].stat3]: "---");
  356.       fprintf(stderr, buf); 
  357.     }
  358.     exit(0);
  359.   }  
  360. #endif
  361.  
  362. }
  363. /* init_exp_obj() - this routine looks through all the assembled
  364.  * archetypes for experience objects then copies their values into
  365.  * the exp_cat[] array. - bt.
  366.  */
  367.  
  368. void init_exp_obj() {
  369.     archetype *at;
  370.  
  371.     nrofexpcat = 0;
  372.     for(at=first_archetype;at!=NULL;at=at->next)
  373.         if(at->clone.type==EXPERIENCE) {
  374.              exp_cat[nrofexpcat] = get_object() ;
  375.          exp_cat[nrofexpcat]->level = 1;
  376.          exp_cat[nrofexpcat]->stats.exp = 0;
  377.              copy_object(&at->clone, exp_cat[nrofexpcat]);
  378.              nrofexpcat++;
  379.              if(nrofexpcat == MAX_EXP_CAT) {
  380.                  LOG(llevError,"Aborting! Reached limit of available experience\n");
  381.                  LOG(llevError,"categories. Need to increase value of MAX_EXP_CAT.\n");
  382.                  exit (0);
  383.              }
  384.         }
  385.  
  386.     if(nrofexpcat == 0) {
  387.         LOG(llevError,"Aborting! No experience objects found in archetypes.\n");
  388.         exit (0);
  389.     } 
  390. }
  391. /* link_skills_to_exp() - this links the skills in skills[] to the appropriate
  392.  * experience category.  Linking is based only on the stats (1,2,3) of the
  393.  * skill. If none of the stats assoc. w/ a skill match those in any experience 
  394.  * object then the skill becomes 'miscellaneous' -b.t.
  395.  */
  396.  
  397. void link_skills_to_exp() {
  398.    int i=0,j=0;
  399.  
  400.         for(i=0;i<NROFSKILLS;i++)
  401.             for(j=0;j<nrofexpcat;j++)
  402.                 if(check_link(skills[i].stat1,exp_cat[j])) {
  403.                         skills[i].category = j;
  404.                         continue;
  405.                 } else if(skills[i].category == EXP_NONE 
  406.             && check_link(skills[i].stat2,exp_cat[j])) {
  407.                         skills[i].category = j;
  408.                         continue;
  409.                 } else if(skills[i].category == EXP_NONE 
  410.             && check_link(skills[i].stat3,exp_cat[j])) {
  411.                         skills[i].category = j;
  412.                         continue;
  413.             /* failed to link, set to EXP_NONE */
  414.                 } else if (j==nrofexpcat || skills[i].stat1 == NO_STAT) {
  415.                         skills[i].category = EXP_NONE;
  416.                         continue;
  417.                 }
  418.  
  419. }
  420.  
  421. /* check_link() - this returns true if the specified stat is set in the
  422.  * experience object. Wish this was more 'generalized'. Added new stats
  423.  * will cause this routine to abort CF. - b.t.
  424.  */
  425.  
  426. int check_link (int stat, object *exp) {
  427.  
  428.         switch (stat) {
  429.             case STR:
  430.                 if(exp->stats.Str) return 1;
  431.                 break;
  432.             case CON:
  433.                 if(exp->stats.Con) return 1;
  434.                 break;
  435.             case DEX:
  436.                 if(exp->stats.Dex) return 1;
  437.                 break;
  438.             case INT:
  439.                 if(exp->stats.Int) return 1;
  440.                 break;
  441.             case WIS:
  442.                 if(exp->stats.Wis) return 1;
  443.                 break;
  444.             case POW:
  445.                 if(exp->stats.Pow) return 1;
  446.         break;
  447.             case CHA:
  448.                 if(exp->stats.Cha) return 1;
  449.                 break;
  450.             case NO_STAT:
  451.                 return 0;
  452.             default:
  453.                 LOG(llevError, "Aborting! Tried to link skill with unknown stat!\n");
  454.                 exit (0);
  455.         }
  456.         return 0;
  457. }        
  458.  
  459. /* read_skill_params() - based on init_spell_params(). This
  460.  * function should read a file 'skill_params' in the /lib 
  461.  * directory.  -b.t.
  462.  *
  463.  *  format of the file 'skill_params' is:
  464.  *    name
  465.  *      EXP_CAT, bexp, lexp, stat1, stat2, stat3
  466.  * 
  467.  *  see the values in lib/skill_params for more inspiration/direction
  468.  */
  469.  
  470. void read_skill_params () {
  471.   FILE *skill_params;
  472.   char fname[MAX_BUF];
  473.   char skill_name[256];
  474.   char skill_attrib[256];
  475.   int cat,bexp,time,stat1,stat2,stat3,skillindex;
  476.   float lexp;
  477.  
  478.   sprintf(fname,"%s/%s",LibDir,"skill_params");
  479.   if(! (skill_params=fopen(fname,"r")))
  480.     {
  481.         perror(fname);
  482.         return;
  483.     }
  484.  
  485.   while(!feof(skill_params))
  486.     {
  487.     /* Large buf, so that long comments don't kill it. */
  488.     fgets(skill_name,255,skill_params);
  489.     if (*skill_name=='#') continue;
  490.         skillindex=lookup_skill_by_name(skill_name);
  491.         if(skillindex == -1) {
  492.             LOG(llevError,"\nskill_params has unrecognized skill: %s",skill_name);
  493.             continue;
  494.         }
  495.         fgets(skill_attrib,255,skill_params);
  496.         sscanf(skill_attrib,"%d %d %d %f %d %d %d",
  497.         &cat,&time,&bexp,&lexp,&stat1,&stat2,&stat3);
  498.         skills[skillindex].category=cat;
  499.         skills[skillindex].time=time;
  500.         skills[skillindex].bexp=bexp;
  501.         skills[skillindex].lexp=lexp;
  502.         skills[skillindex].stat1=stat1;
  503.         skills[skillindex].stat2=stat2;
  504.         skills[skillindex].stat3=stat3;
  505.     }
  506. }
  507.  
  508. /* lookup_skill_by_name() - based on look_up_spell_by_name - b.t. */
  509.  
  510. int lookup_skill_by_name(char *string) {
  511.   int skillnr=0, nmlen;
  512.   char name[MAX_BUF];
  513.  
  514.   if(!string) return -1;
  515.   
  516.   /* truncate string at first blank space to find skill name. */
  517.   nmlen = strcspn(string," ");
  518.   strncpy(name,string,nmlen); 
  519.  
  520.   while(strncmp(name,skills[skillnr].name,MIN(strlen(skills[skillnr].name),
  521.     nmlen)) && skillnr < (NROFSKILLS-1) )
  522.   {
  523.     skillnr++;
  524.   }
  525.  
  526.   if(skillnr==NROFSKILLS) skillnr=-1; 
  527.   return skillnr;
  528. }
  529.  
  530. /* check_skill_to_fire() -  */
  531.  
  532. int check_skill_to_fire(object *who) {
  533.   int skillnr=0;
  534.   rangetype shoottype=range_none;
  535.  
  536.   if(who->type!=PLAYER) return 1; 
  537.  
  538.   switch((shoottype = who->contr->shoottype)) {
  539.     case range_bow:
  540.        skillnr = SK_MISSILE_WEAPON;
  541.        break;
  542.     case range_none:
  543.     case range_skill:
  544.        return 1; 
  545.        break;
  546.     case range_magic:
  547.         if(spells[who->contr->chosen_spell].cleric)
  548.             skillnr = SK_PRAYING;
  549.         else
  550.             skillnr = SK_SPELL_CASTING;
  551.         break;
  552.     case range_scroll:
  553.     case range_rod:
  554.     case range_horn:
  555.     case range_wand:
  556.        skillnr = SK_USE_MAGIC_ITEM;
  557.        break;
  558.     default:
  559.        LOG(llevError,"Warning: bad call of check_skill_to_fire()\n");
  560.        return 0;
  561.   }
  562.   if (change_skill(who,skillnr)) { 
  563. #ifdef SKILL_UTIL_DEBUG
  564.       LOG(llevDebug,"check_skill_to_fire(): got skill:%s for %s\n"
  565.         ,skills[skillnr].name,who->name);
  566. #endif
  567.     who->contr->shoottype=shoottype;
  568.     return 1;
  569.   } else 
  570.     return 0;
  571. }
  572.  
  573. /* check_skill_to_apply() - When a player tries to use an
  574.  * object which requires a skill this function is called.
  575.  * (examples are weapons like swords and bows)
  576.  * It does 2 things: checks for appropriate skill in player inventory
  577.  * and alters the player status by making the appropriate skill
  578.  * the new chosen_skill. This routine works for both players
  579.  * applying and unapplying objects. Returns true if the skill exists
  580.  * and could be readied or we successfully unapplyied an object.
  581.  * -bt. thomas@astro.psu.edu
  582.  */
  583.  
  584. /* IMPORTANT NOTE: always call this fctn *after* FLAG_APPLY is set/unset
  585.  * and *before* change_abil() is called.
  586.  */
  587.  
  588. int check_skill_to_apply(object *who, object *item) {
  589.     int skill=0;
  590.     rangetype shoottype=range_none;
  591.  
  592.     if(who->type!=PLAYER) return 1; /* this fctn only for players */
  593.  
  594. /* first figure out the required skill and ending shoottype          
  595.  * from the item */
  596.  
  597.     switch(item->type) {
  598.       case WEAPON:
  599.         skill = SK_MELEE_WEAPON;
  600.         shoottype = range_skill;
  601.         break;
  602.       case BOW:
  603.         skill = SK_MISSILE_WEAPON;
  604.         shoottype = range_bow;
  605.         break;
  606.       case POTION:
  607.       case SCROLL:
  608.         skill = SK_USE_MAGIC_ITEM; /* not literacy! */
  609.         shoottype = range_scroll;
  610.     break;
  611.       case ROD:
  612.         skill = SK_USE_MAGIC_ITEM;
  613.         shoottype = range_rod; 
  614.     break;
  615.       case WAND:                
  616.         skill = SK_USE_MAGIC_ITEM;
  617.         shoottype = range_wand; 
  618.         break;
  619.       case HORN:                
  620.         skill = SK_USE_MAGIC_ITEM;
  621.         shoottype = range_horn;
  622.         break;
  623.       default:
  624.         LOG(llevDebug,"Warning: bad call of check_skill_to_apply()");
  625.         LOG(llevDebug,"No skill exists for item: %s",query_name(item));
  626.         return 0;
  627.     }
  628.  
  629. /* now we alter player status appropriately */
  630.  
  631.     if(!QUERY_FLAG(item,FLAG_APPLIED)) {     /* we are trying to unapply */
  632.  
  633.         if(who->chosen_skill && who->chosen_skill->stats.sp == skill)
  634.            (void) change_skill(who,-1);
  635.         return 0;
  636.  
  637.     } else {                               /* we are trying to apply */
  638.  
  639.         if(!who->chosen_skill || (who->chosen_skill
  640.              && who->chosen_skill->stats.sp!=skill))
  641.                 if(!change_skill(who,skill)) {
  642.                     new_draw_info(NDI_UNIQUE, 0,who,"You can't use that!");
  643.                     CLEAR_FLAG(item,FLAG_APPLIED);
  644.                     return 0;
  645.                 }
  646.     }
  647.  
  648.     who->contr->shoottype=shoottype;
  649.     return 1;
  650. }
  651.  
  652. /* init_player_exp() - makes various checks and initialization of player 
  653.  * experience objects. If things aren't cool then we change them here.
  654.  *  -b.t.
  655.  */
  656.  
  657. int init_player_exp(object *pl) {
  658.   int i,j,exp_index=0;
  659.   object *tmp,*exp_ob[MAX_EXP_CAT];
  660.  
  661.    if(pl->type!=PLAYER) {
  662.         LOG(llevError, "init_player_exp(): called non-player %s.\n",pl->name);
  663.     return 0;
  664.    }
  665.    /* first-pass find all current exp objects */
  666.    for(tmp=pl->inv;tmp;tmp=tmp->below)
  667.       if(tmp->type==EXPERIENCE) {
  668.            exp_ob[exp_index] = tmp;
  669.            exp_index++;
  670.       } else if (exp_index == MAX_EXP_CAT)
  671.            return 0;     
  672.  
  673.    /* second - if pl has wrong nrof experience objects
  674.     * then give player a complete roster.
  675.     */   
  676.  
  677.   /*  No exp objects - situation for a new player 
  678.    *  or pre-skills/exp code player file */
  679.    if(!exp_index) {  
  680.         for(j=0;j<nrofexpcat;j++) {
  681.            tmp = get_object();
  682.            copy_object(exp_cat[j],tmp);
  683.            insert_ob_in_ob(tmp,pl);
  684.        tmp->stats.exp = pl->stats.exp/nrofexpcat;
  685.            exp_ob[j] = tmp;
  686.            if (pl->contr->eric_server > 0)
  687.                esrv_send_item(pl, tmp);
  688.            exp_index++;
  689.         }
  690.   } else if (exp_index!=nrofexpcat) { /* situation for old save file */
  691.      if(exp_index<nrofexpcat) 
  692.          LOG(llevError,"init_player_exp(): player has too few experience objects.\n");
  693.      if(exp_index>nrofexpcat) 
  694.          LOG(llevError,"init_player_exp(): player has too many experience objects.\n");
  695.      for(j=0;j<nrofexpcat;j++)
  696.          for(i=0;i<=exp_index;i++)
  697.            if(!strcmp(exp_ob[i]->name,exp_cat[j]->name)) {
  698.               break;
  699.            } else if(i==exp_index) {
  700.               tmp = get_object();
  701.               copy_object(exp_cat[j],tmp);
  702.               insert_ob_in_ob(tmp,pl);
  703.           exp_ob[i] = tmp; 
  704.               if (pl->contr->eric_server > 0)
  705.                   esrv_send_item(pl, tmp);
  706.               exp_index++;
  707.            }
  708.    }
  709.  
  710.    /* now we loop through one more time and set "apply"
  711.     * flag on valid expericence objects. Fix_player()
  712.     * requires this, and we get the bonus of being able
  713.     * to ignore invalid experience objects in the player
  714.     * inventory (player file from another game set up?).
  715.     * Also, we reset the score (or "total" experience) of 
  716.     * the player to be the sum of all valid experience 
  717.     * objects.
  718.     */
  719.  
  720.    pl->stats.exp = 0;
  721.    for(i=0;i<exp_index;i++) { 
  722.     if(!QUERY_FLAG(exp_ob[i],FLAG_APPLIED)) 
  723.            SET_FLAG(exp_ob[i],FLAG_APPLIED); 
  724.     pl->stats.exp += exp_ob[i]->stats.exp;
  725.     player_lvl_adj(NULL, exp_ob[i]);
  726.    } 
  727.    return 1;
  728. }
  729.  
  730.  
  731. /* unlink_skill() - removes skill from a player skill list and
  732.  * unlinks the pointer to the exp object */ 
  733.  
  734. void unlink_skill(object *skillop) {
  735.   object *op=skillop?skillop->env:NULL;
  736.  
  737.   if(!op||op->type!=PLAYER) { 
  738.     LOG(llevError,"Error: unlink_skill() called for non-player!");
  739.     return;
  740.   }
  741.  
  742. #ifdef LINKED_SKILL_LIST
  743.   if(remove_skill_from_list(op,skillop))
  744. #endif
  745.       skillop->exp_obj = NULL;
  746. }
  747.  
  748. int remove_skill_from_list(object *op, object *skillop) {
  749.   objectlink *obl=op->sk_list,*first,*prev=NULL,*nxt;
  750.  
  751.   if(!skillop) return 0;
  752.  
  753.   prev=first=obl;
  754.   while(obl) {
  755.      nxt = obl->next ? obl->next: NULL;
  756.      if(obl->id==skillop->stats.sp) {
  757.           if(obl==first) {
  758.               op->sk_list = nxt;
  759.               nxt = (nxt&&nxt->next) ? nxt->next: NULL;
  760.           }
  761.           if(prev) prev->next = nxt;
  762. #if 0
  763.           LOG(llevDebug,"Removing skill: %s from list\n",
  764.                 skillop->name);
  765. #endif
  766.           CFREE(obl);
  767.           obl = NULL;
  768.           return 1;
  769.      } else
  770.           prev = obl;
  771.      obl=nxt;
  772.   }   
  773.      
  774.   if(first) op->sk_list = first;
  775.   return 0; 
  776. }
  777.  
  778. /* link_player_skills() - linking skills with experience objects
  779.  * and creating a linked list of skills for later fast access.
  780.  *
  781.  * Returns true if successfull. To be usefull this routine has to
  782.  * be called *after* init_player_exp() and give_initial_items()
  783.  *
  784.  * Aug 95 added feature whereby old player save files will get a
  785.  * (needed) basic assortment of skills (see basic_skill array).
  786.  * 
  787.  * - b.t. thomas@astro.psu.edu
  788.  */
  789.  
  790. int link_player_skills(object *pl) {
  791.   archetype *at=NULL;
  792.   int i,j,cat=0,sk_index=0,exp_index=0,old_file=1;
  793.   object *tmp,*sk_ob[100],*exp_ob[MAX_EXP_CAT];
  794.   static char *basic_skills[] = { "skill_melee_weapon",
  795.                "skill_missile_weapon",
  796.                "skill_use_magic_item",
  797.                "skill_praying",
  798.                "skill_spellcasting",
  799.                "skill_find_traps",
  800.                "skill_remove_trap"}; 
  801.  
  802.   /* first find all exp and skill objects */
  803.  
  804.    for(tmp=pl->inv;tmp&&sk_index<100;tmp=tmp->below)
  805.       if(tmp->type==EXPERIENCE) {
  806.            exp_ob[exp_index] = tmp;
  807.            exp_index++;
  808.       } else if (tmp->type==SKILL) {
  809.        sk_ob[sk_index]=tmp; 
  810.        sk_index++;
  811.        if(!strcmp(tmp->name,skills[SK_USE_MAGIC_ITEM].name)) old_file = 0;
  812.       } 
  813.  
  814.    if(exp_index!=nrofexpcat) {
  815.        LOG(llevError,"link_player_skills() - player has bad number of exp obj\n");
  816.        if(!init_player_exp(pl)) { 
  817.           LOG(llevError,"link_player_skills() - failed to correct problem.\n");
  818.       return 0;
  819.        }
  820.        (void) link_player_skills(pl);
  821.        return 1;
  822.    } 
  823.  
  824.    /* This looks like we have an old player file, so give those basic
  825.     * skills to the player in this case. 
  826.     */
  827.    if(old_file) { 
  828.     object *tmp2;
  829.     int limit = sizeof(basic_skills)/sizeof(char *);
  830.  
  831.     /* this is solely for old characters */
  832.     if(pl->contr->orig_stats.Pow==0) { 
  833.         pl->stats.Pow = pl->stats.Int;
  834.         pl->contr->orig_stats.Pow = pl->contr->orig_stats.Int;
  835.     }
  836.  
  837.     for(i=0;i<limit;i++) 
  838.        if((at=find_archetype(basic_skills[i]))!=NULL) {
  839.           int add = 1;
  840.           tmp = arch_to_object(at);
  841.           /* check if they already have this (basic) skill */
  842.           for(tmp2=pl->inv;tmp2;tmp2=tmp2->below)
  843.         if(tmp2->type==SKILL && !strcmp(tmp2->name,tmp->name)) {
  844.             add = 0;
  845.             break;
  846.         }
  847.  
  848.               if(add) { 
  849.         insert_ob_in_ob(tmp,pl);
  850.                   sk_ob[sk_index] = tmp;
  851.               sk_index++;
  852.                   LOG(llevDebug,"Added basic skill: %s to inventory of %s\n",
  853.             basic_skills[i], pl->name);
  854.           }
  855.        } else { 
  856.               LOG(llevError, 
  857.         "init_player_skills() - can't find basic skill: %s\n",basic_skills[i]);
  858.           return 0;
  859.        } 
  860.   }
  861.   /* Ok, create linked list and link the associated skills to exp objects */
  862.    for(i=0;i<sk_index;i++) {
  863. #ifdef LINKED_SKILL_LIST
  864.       objectlink *obl;
  865.     obl = (objectlink *) malloc(sizeof(objectlink));
  866.     obl->ob=sk_ob[i];
  867.     obl->id=sk_ob[i]->stats.sp; 
  868.     obl->next = pl->sk_list;
  869.     pl->sk_list = obl;
  870. #endif
  871.         cat = skills[sk_ob[i]->stats.sp].category;
  872.     if(cat==EXP_NONE) continue;
  873.  
  874.         for(j=0;exp_ob[j]!=NULL&&j<exp_index;j++)
  875.            if(!strcmp(exp_cat[cat]->name,exp_ob[j]->name)) break;
  876.  
  877.         sk_ob[i]->exp_obj = exp_ob[j];
  878.    }
  879.    return 1;
  880. }   
  881.  
  882. /* link_player_skill() - links a  skill to exp object when applied or learned by
  883.  * a player. Returns true if can link. Returns false if got misc
  884.  * skill - bt.
  885.  */ 
  886.  
  887. int link_player_skill(object *pl, object *skillop) {
  888.   object *tmp;
  889.   int cat;
  890. #ifdef LINKED_SKILL_LIST
  891.   objectlink *obl;
  892.  
  893.   /* add it to the linked list */
  894.   obl = (objectlink *) malloc(sizeof(objectlink));
  895.   obl->ob=skillop;
  896.   obl->id=skillop->stats.sp;
  897.   obl->next = pl->sk_list;
  898.   pl->sk_list = obl;
  899. #endif
  900.  
  901.   cat = skills[skillop->stats.sp].category; 
  902.  
  903.   if(cat!=EXP_NONE) {   /* ok the skill has an exp object, now find right one in pl inv */ 
  904.       for(tmp=pl->inv;tmp;tmp=tmp->below)
  905.           if(tmp->type==EXPERIENCE&&!strcmp(exp_cat[cat]->name,tmp->name)) {    
  906.               skillop->exp_obj = tmp;
  907.           break;
  908.        }
  909.       return 1;
  910.   }
  911.   skillop->exp_obj = NULL;
  912.  
  913.   return 0; 
  914. }
  915.  
  916. /* Learn skill. This inserts the requested skill in the player's
  917.  * inventory. The 'slaying' field of the scroll should have the
  918.  * exact name of the requested archetype (there should be a better way!?)
  919.  * -bt. thomas@nomad.astro.psu.edu
  920.  */
  921.  
  922. int
  923. learn_skill (object *pl, object *scroll) {
  924. object *tmp;
  925. archetype *skill;
  926. #ifdef LINKED_SKILL_LIST
  927. objectlink *obl;
  928. #else
  929. object *tmp2;
  930. #endif
  931.       skill=find_archetype(scroll->slaying);
  932.      if(!skill)
  933.            return 2;
  934.      tmp=arch_to_object(skill);
  935.      if(!tmp) return 2;
  936.  
  937. /* check if player already has it */
  938. #ifdef LINKED_SKILL_LIST
  939.      for(obl=pl->sk_list;obl;obl=obl->next)
  940.     if(obl->ob->invisible
  941.      &&(obl->ob->stats.sp==tmp->stats.sp)) return 0;
  942. #else /* LINKED_SKILL_LIST */ 
  943.      for(tmp2=pl->inv;tmp2;tmp2=tmp2->below)
  944.     if(tmp2->type==SKILL&&tmp2->invisible
  945.      &&tmp2->stats.sp==tmp->stats.sp) return 0;
  946. # endif /* LINKED_SKILL_LIST */
  947.  
  948.      /* now a random change to learn, based on player Int */
  949.      if(RANDOM()%100>learn_spell[pl->stats.Int])
  950.        return 2; /* failure :< */
  951.  
  952.     /* Everything is cool. Give'em the skill */
  953.      (void) insert_ob_in_ob(tmp,pl);
  954.      (void) link_player_skill(pl,tmp);  
  955.      if (pl->contr->eric_server > 0)
  956.     esrv_send_item(pl, tmp);
  957.      return 1;
  958. }
  959.  
  960. /* show_skills() - Meant to allow players to examine
  961.  * their current skill list. b.t. (thomas@nomad.astro.psu.edu)
  962.  * I have now added capability to show assoc. experience objects too.
  963.  * Inportant note: the value of chosen_skill->level is not always
  964.  * accurate--it is only updated in change_skill(), therefore long
  965.  * unused skills may have wrong value. This is why we call 
  966.  * skill->exp_obj->level instead (the level of the associated experience
  967.  * object -- which is always right).
  968.  * -b.t.
  969.  */
  970.  
  971. void show_skills(object *op) {
  972.   object *tmp=NULL;
  973.   char buf[MAX_BUF], *in;
  974.   int i,length,is_first;
  975. #ifdef LINKED_SKILL_LIST
  976.   objectlink *obl;
  977.  
  978.   if (!op->sk_list) {
  979.       new_draw_info(NDI_UNIQUE, 0,op,"You know no skills.");
  980.       op->chosen_skill=NULL;    /* double safety - for those who lose skills */
  981.       return;
  982.   } else {
  983. #endif
  984.       length = INFOCHARS-19;
  985.       in = "";
  986.       if (op)
  987.         clear_win_info(op);
  988.       /* sprintf(buf,"Player Skills      Category    /lvl"); */ 
  989.       sprintf(buf,"Player skills by experience category");
  990.       new_draw_info(NDI_UNIQUE, 0,op,buf);
  991. #ifdef LINKED_SKILL_LIST
  992.   }
  993. #endif
  994.  
  995. /* print out skills by category */
  996.   for (i=0;i<=nrofexpcat;i++) { 
  997.     is_first=1;
  998.     if(i==nrofexpcat) i=EXP_NONE; /* skip to misc exp category */
  999. #ifdef LINKED_SKILL_LIST
  1000.     for (obl=op->sk_list;obl;obl=obl->next) {
  1001.       tmp = obl->ob;
  1002. #else
  1003.     for (tmp=op->inv;tmp;tmp=tmp->below) {
  1004.       if(tmp->type!=SKILL) continue;
  1005. #endif
  1006.       if(skills[tmp->stats.sp].category==i
  1007.        &&(tmp->invisible||QUERY_FLAG(tmp,FLAG_APPLIED))) {
  1008.       /* header info */
  1009.           if(is_first) {  
  1010.             is_first=0;
  1011.             new_draw_info(NDI_UNIQUE, 0,op," "); 
  1012.             if(tmp->exp_obj) { 
  1013.           object *tmp_exp = tmp->exp_obj;
  1014.           int k=(length-15-strlen(tmp_exp->name)); 
  1015.           char tmpbuf[40];
  1016.           strcpy(tmpbuf,tmp_exp->name);
  1017.           while(k>0) {k--; strcat(tmpbuf,".");} 
  1018.               new_draw_info_format(NDI_UNIQUE,0,op,"%slvl:%3d (xp:%d)",
  1019.         tmpbuf,tmp_exp->level,tmp_exp->stats.exp);
  1020.             } else if(i==EXP_NONE) { 
  1021.               new_draw_info(NDI_UNIQUE,0,op,"misc.");
  1022.         } 
  1023.       }
  1024.  
  1025.       /* print matched skills */
  1026.           if((!op || QUERY_FLAG(op, FLAG_WIZ)))
  1027.               (void) sprintf(buf,"%s%s %s (%5d)",in,
  1028.         QUERY_FLAG(tmp,FLAG_APPLIED)?"*":"-",
  1029.         skills[tmp->stats.sp].name,tmp->count);
  1030.           else
  1031.               (void) sprintf(buf,"%s%s %s",in,
  1032.         QUERY_FLAG(tmp,FLAG_APPLIED)?"*":"-",
  1033.         skills[tmp->stats.sp].name);
  1034.  
  1035.           new_draw_info(NDI_UNIQUE,0,op,buf);
  1036.       } 
  1037.     }
  1038.   }
  1039. }    
  1040.  
  1041. /* use_skill() - similar to invoke command, it executes the skill in the 
  1042.  * direction that the user is facing. Returns false if we are unable to 
  1043.  * change to the requested skill, or were unable to use the skill properly. 
  1044.  * -b.t.
  1045.  */
  1046.  
  1047. int use_skill(object *op, char *params) {
  1048.   int skill=lookup_skill_by_name(params);
  1049.  
  1050.   if(change_skill(op,skill)) 
  1051.     if(do_skill(op,op->facing,params))
  1052.         return 1;
  1053.   return 0; 
  1054. }
  1055.  
  1056. /* change_skill() - returns true if we are able to change to the requested
  1057.  * skill. Ignore the 'pl' designation, this code is useful for players and
  1058.  * monsters.  -bt. thomas@astro.psu.edu
  1059.  */
  1060.  
  1061. /* Sept 95. Got rid of nasty strcmp calls in here -b.t.*/
  1062.  
  1063. /* Dec 95 - cleaned up the code a bit, change_skill now passes an indexed
  1064.  * value for the skill rather than a character string. Added find_skill. 
  1065.  * -b.t. */
  1066.  
  1067. int change_skill(object *pl, int sk_index) {
  1068.   object *tmp; 
  1069.  
  1070.   /* unapply old skill, do this before returning */
  1071.   if(pl->chosen_skill) {
  1072.     if(pl->chosen_skill->stats.sp==sk_index  
  1073.        && QUERY_FLAG(pl->chosen_skill,FLAG_APPLIED)) { /* already have it prepared */ 
  1074.             pl->contr->shoottype = range_skill;
  1075.             draw_stats(pl);
  1076.         return 1;    
  1077.     }
  1078.     /* need to do because some skills set path_attuned, slaying, etc */
  1079.         CLEAR_FLAG(pl->chosen_skill,FLAG_APPLIED);
  1080.     (void) change_abil(pl,pl->chosen_skill);
  1081.         if(QUERY_FLAG(pl,FLAG_READY_SKILL))
  1082.             CLEAR_FLAG(pl,FLAG_READY_SKILL);
  1083.     if(!pl->chosen_skill->invisible) /* its a tool, need to unlink it */
  1084.         unlink_skill(pl->chosen_skill);
  1085.         pl->chosen_skill = NULL;
  1086.   }
  1087.  
  1088.   if(sk_index<0) { /* bye,bye. We didn't request a valid skill */
  1089.     new_draw_info(NDI_UNIQUE, 0,pl,"You can't use an non-existent skill!");
  1090.     return 0;
  1091.   }
  1092.  
  1093.   /* ok, look for the requested skill */
  1094.   if((tmp=find_skill(pl,sk_index))) { /* yes, pl does have named skill */
  1095.         pl->chosen_skill = tmp;
  1096.  
  1097.         /* Update skill level from exp obj */
  1098.         if (tmp->exp_obj)
  1099.          pl->chosen_skill->level=tmp->exp_obj->level;
  1100.  
  1101.            if(!tmp->invisible&&!QUERY_FLAG(tmp,FLAG_APPLIED)) { /* for tools */ 
  1102.                 if(pl->type==PLAYER) {
  1103.                    new_draw_info_format(NDI_UNIQUE, 0, pl,
  1104.                        "You can now use the skill: %s.", skills[sk_index].name);
  1105.                    if(!tmp->exp_obj)
  1106.                (void) link_player_skill(pl, tmp); 
  1107.             }
  1108.         } 
  1109.  
  1110.         if(!QUERY_FLAG(tmp,FLAG_APPLIED))
  1111.                      SET_FLAG(tmp, FLAG_APPLIED);
  1112.             if(!QUERY_FLAG(pl,FLAG_READY_SKILL)) 
  1113.              SET_FLAG(pl,FLAG_READY_SKILL);
  1114.         (void) change_abil(pl,tmp);
  1115.             fix_player(pl);
  1116.  
  1117.             if(pl->type==PLAYER) {
  1118.                  new_draw_info_format(NDI_UNIQUE, 0,pl, 
  1119.             "Readied skill: %s.",skills[tmp->stats.sp].name);
  1120.                  pl->contr->shoottype = range_skill;
  1121.                  draw_stats(pl);
  1122.          draw_inventory(pl);
  1123.             }
  1124.             return 1;
  1125.   }       
  1126.  
  1127.   if(QUERY_FLAG(pl,FLAG_READY_SKILL))   /* shouldnt happen -b.t. */
  1128.         CLEAR_FLAG(pl, FLAG_READY_SKILL);
  1129.  
  1130.   if(pl->type!=PLAYER) return 0;
  1131.  
  1132. /* Player stuff for when we didnt ready the requested skill. This can be
  1133.  * either because we have requested a non-existent skill or because
  1134.  * we selected negative skill index. The 'negative' option mostly occurs 
  1135.  * when change_skill is called to silently change player skill to NULL. 
  1136.  * For example, unwielding a weapon--change_skill() called from 
  1137.  * apply_special().
  1138.  */
  1139.  
  1140.   if(sk_index>=0 && sk_index<NROFSKILLS)
  1141.      new_draw_info_format(NDI_UNIQUE, 0,pl,"You have no knowledge of %s.",
  1142.     skills[sk_index].name);
  1143.  
  1144.   pl->contr->shoottype = range_none;
  1145.  
  1146.   return 0;
  1147. }
  1148.  
  1149. /* attack_melee_weapon() - this handles melee weapon attacks -b.t.
  1150.  * For now we are just checking to see if we have a ready weapon here.
  1151.  * But there is a real neato possible feature of this scheme which
  1152.  * bears mentioning:
  1153.  * Since we are only calling this from do_skill() in the future
  1154.  * we may make this routine handle 'special' melee weapons attacks
  1155.  * (like disarming manuever with sai) based on player SK_level and
  1156.  * weapon type.
  1157.  */
  1158.  
  1159. int attack_melee_weapon(object *op, int dir, char *string) {
  1160.  
  1161.   if(!QUERY_FLAG(op, FLAG_READY_WEAPON)) {
  1162.         if(op->type==PLAYER)
  1163.           new_draw_info(NDI_UNIQUE, 0,op,"You have no ready weapon to attack with!");
  1164.         return 0;
  1165.     }
  1166.  
  1167.   return skill_attack(NULL,op,dir,string);
  1168.  
  1169. }
  1170.  
  1171. /* attack_hth() - this handles all hand-to-hand attacks -b.t. */
  1172.  
  1173. /* July 5, 1995 - I broke up attack_hth() into 2 parts. In the first
  1174.  * (attack_hth) we check for weapon use, etc in the second (the new
  1175.  * function skill_attack() we actually attack.
  1176.  */
  1177.  
  1178. int attack_hth(object *pl, int dir, char *string) {
  1179.   object *enemy=NULL,*weapon;
  1180.  
  1181.   if(QUERY_FLAG(pl, FLAG_READY_WEAPON))
  1182.     for(weapon=pl->inv;weapon;weapon=weapon->below) {
  1183.         if(weapon->type!=WEAPON
  1184.           ||!QUERY_FLAG(weapon, FLAG_APPLIED)) continue;
  1185.         CLEAR_FLAG(weapon,FLAG_APPLIED);
  1186.         CLEAR_FLAG(pl,FLAG_READY_WEAPON);
  1187.         if(pl->type==PLAYER) {
  1188.           new_draw_info(NDI_UNIQUE, 0,pl,"You unweild your weapon in order to attack.");
  1189.           fix_player(pl);
  1190.           draw_stats(pl);
  1191.           draw_inventory(pl);
  1192.         }
  1193.         break;
  1194.     }   
  1195.  
  1196.   return skill_attack(enemy,pl,dir,string);
  1197.  
  1198. }
  1199.  
  1200. /* skill_attack() - Core routine for use when we attack using a skills
  1201.  * system. There are'nt too many changes from before, basically this is
  1202.  * a 'wrapper' for the old attack system. In essence, this code handles
  1203.  * all skill-based attacks, ie hth, missile and melee weapons should be
  1204.  * treated here. If an opponent is already supplied by move_player(),
  1205.  * we move right onto do_skill_attack(), otherwise we find if an
  1206.  * appropriate opponent exists.
  1207.  *
  1208.  * This is called by move_player() and attack_hth()
  1209.  *
  1210.  * Initial implementation by -bt thomas@astro.psu.edu
  1211.  */
  1212.  
  1213. int skill_attack (object *tmp, object *pl, int dir, char *string) {
  1214.   int dx,dy;
  1215.  
  1216.   if(!dir) dir=pl->facing;
  1217.   dx=freearr_x[dir],dy=freearr_y[dir];
  1218.  
  1219. /* If we don't yet have an opponent, find if one exists, and attack.
  1220.  * Legal opponents are the same as outlined in move_player_attack()
  1221.  */
  1222.  
  1223.   if(tmp==NULL)
  1224.     for(tmp=get_map_ob(pl->map,pl->x+dx,pl->y+dy);tmp;tmp=tmp->above)
  1225.         if((QUERY_FLAG(tmp,FLAG_ALIVE) && tmp->stats.hp>=0)
  1226.            || QUERY_FLAG(tmp, FLAG_CAN_ROLL)
  1227.            || tmp->type==LOCKED_DOOR ) {
  1228. #ifdef SIMPLE_PARTY_SYSTEM
  1229.                 if((pl->type==PLAYER && tmp->type==PLAYER) && (pl->contr->party_number!=-1
  1230.                        && pl->contr->party_number==tmp->contr->party_number))
  1231.                                 return 0;
  1232. #endif
  1233.                 break;
  1234.         }
  1235.  
  1236.   if(tmp!=NULL) return do_skill_attack(tmp,pl,string);
  1237.  
  1238.   if(pl->type==PLAYER)
  1239.     new_draw_info(NDI_UNIQUE, 0,pl,"There is nothing to attack!");
  1240.  
  1241.   return 0;
  1242. }
  1243.  
  1244. /* do_skill_attack() - We have got an appropriate opponent from either
  1245.  * move_player_attack() or skill_attack(). In this part we get on with
  1246.  * attacking, take care of messages from the attack and changes in invisible.
  1247.  * Returns true if the attack damaged the opponent.
  1248.  * -b.t. thomas@astro.psu.edu
  1249.  */
  1250.  
  1251. int do_skill_attack(object *tmp, object *op, char *string) {
  1252.     int success; 
  1253.     object *pskill = op->chosen_skill;
  1254.     char buf[MAX_BUF], *name = query_name(tmp);
  1255.     char *attack_hth_skill[] = {"skill_karate",    "skill_clawing",
  1256.     "skill_flame_touch", "skill_punching", NULL };
  1257.  
  1258.    /* For Players only: if there is no ready weapon, and no "attack" skill
  1259.     * is readied either then check if op has a hand-to-hand "attack skill" 
  1260.     * to use instead. We define this an "attack" skill as any skill 
  1261.     * that is linked to the strexpobj.
  1262.     * If we are using the wrong skill, change it (if possible) to
  1263.     * a more appropriate skill (in order from the attack_hth_skill[]
  1264.     * list). If the miserable slob still does'nt have any of the hth 
  1265.     * skills, give them the last attack_hth_skill on the list. There 
  1266.     * is probably a better way to do this...how?? 
  1267.     */
  1268.  
  1269.     if(op->type==PLAYER 
  1270.     && !QUERY_FLAG(op,FLAG_READY_WEAPON)
  1271.     && ((!pskill||!pskill->exp_obj)||!pskill->exp_obj->stats.Str)) { 
  1272.         int i=0,got_one=0;
  1273.         object *tmp2=NULL;
  1274.         while(attack_hth_skill[i]!=NULL) { 
  1275.         for(tmp2=op->inv;tmp2;tmp2=tmp2->below) 
  1276.             if(tmp2->type==SKILL 
  1277.             && !strcmp(attack_hth_skill[i],tmp2->arch->name)) {
  1278.                 got_one=1;
  1279.                 break;
  1280.             }
  1281.          if(got_one) break;
  1282.         i++;
  1283.         }
  1284.  
  1285.         if(!got_one) { /* Arrgh. The player has no hth attack skills in inventory. 
  1286.                 * Lets give them the last one in attack_hth_skill[] */ 
  1287.         archetype *skill;
  1288.         skill=find_archetype(attack_hth_skill[i-1]);
  1289.                  if(!skill) { 
  1290.                 LOG(llevError,"do_skill_attack() could'nt find attack skill for %s\n",
  1291.             op->name);
  1292.             return 0;
  1293.         } 
  1294.            
  1295.                 tmp2=arch_to_object(skill);
  1296.         insert_ob_in_ob(tmp2,op);
  1297.         (void) link_player_skill(op,tmp2);
  1298.              if (op->contr->eric_server > 0)
  1299.                 esrv_send_item(op, tmp2);
  1300.         }
  1301.         /* now try to ready the new skill */
  1302.         if(!change_skill(op,tmp2->stats.sp)) {  /* oh oh, trouble! */
  1303.             LOG(llevError,"do_skill_attack() could'nt give new hth skill to %s\n",
  1304.             op->name);
  1305.         return 0;
  1306.         } 
  1307.     }     
  1308.  
  1309.    /* check if op is using a hth attack, if so modify damage */
  1310.           if(QUERY_FLAG(op,FLAG_READY_SKILL) && op->chosen_skill
  1311.              && !QUERY_FLAG(op, FLAG_READY_WEAPON))
  1312.                  if((op->type!=PLAYER) || (op->type==PLAYER
  1313.                         && op->contr->shoottype==range_skill)) {
  1314.                int dam=hth_damage(tmp,op); 
  1315.                            if(dam!=op->stats.dam) { 
  1316.                 op->stats.dam=dam;
  1317.                 draw_stats(op);
  1318.                }
  1319.       }
  1320.  
  1321.    /* if we have 'ready weapon' but no 'melee weapons' skill readied
  1322.     * this will flip to that skill. This is only window dressing for
  1323.     * the players--no need to do this for monsters.
  1324.     */
  1325.           if(op->type==PLAYER && QUERY_FLAG(op,FLAG_READY_WEAPON)
  1326.              && (!op->chosen_skill || op->chosen_skill->stats.sp!=SK_MELEE_WEAPON)) {
  1327. #ifdef NO_AUTO_SKILL_SWITCH
  1328.         rangetype oldrange=op->contr->shoottype;
  1329. #endif
  1330.                  (void) change_skill(op,SK_MELEE_WEAPON);
  1331.  
  1332. /* This is just a simple hack - would probably be cleaner to have change_skill
  1333.  * do the right thing, but this isn't that bad.
  1334.  */
  1335. #ifdef NO_AUTO_SKILL_SWITCH
  1336.         if (op->contr->shoottype!=oldrange) {
  1337.             op->contr->shoottype=oldrange;
  1338.             draw_stats(op);
  1339.         }
  1340. #endif
  1341.       }
  1342.  
  1343.     /* lose invisiblity/hiding status for running attacks */
  1344.  
  1345.           if(op->type==PLAYER && op->contr->tmp_invis) {
  1346.                  op->contr->tmp_invis=0;
  1347.                  op->invisible=0;
  1348.                  op->hide=0;
  1349.                  update_object(op);
  1350.           }
  1351.  
  1352.           success = attack_ob(tmp,op);
  1353.  
  1354.    /* print appropriate  messages to the player */
  1355.  
  1356.           if(success && string!=NULL) {
  1357.                  sprintf(buf, string);
  1358.                  if(op->type==PLAYER)
  1359.                         new_draw_info_format(NDI_UNIQUE, 0,op,
  1360.                           "You %s %s!",buf,name);
  1361.                  else if(tmp->type==PLAYER)
  1362.                         new_draw_info_format(NDI_UNIQUE, 0,tmp,
  1363.                            "%s %s you!",query_name(op),buf);
  1364.           }
  1365.  
  1366.           return success;
  1367.  
  1368. }                         
  1369.  
  1370.  
  1371. /* hth_damage() - returns the appropriate amount of hth damage, based on the
  1372.  * skill in use and the opponent's characteristics. This is generalized to
  1373.  * allow monster skill use too. Called only from do_skill_attack().  -b.t.
  1374.  */
  1375.  
  1376. int hth_damage(object *target, object *pl) {
  1377.   int base = pl->arch->clone.stats.dam + dam_bonus[pl->stats.Str];
  1378.   int damage = pl->chosen_skill->stats.dam * (1 + (SK_level(pl)/9));
  1379.  
  1380.   if(damage<1)                  /* Safety - not a hth skill per se */
  1381.         return base;
  1382.   else
  1383.         damage += base;
  1384.  
  1385.   /* are we using a skill?, if not return normal hth damage */
  1386.   if(pl->chosen_skill==NULL
  1387.         || !QUERY_FLAG(pl,FLAG_READY_SKILL)) return damage;
  1388.  
  1389.  /* Against some creatures the hth attack gets no special bonus.
  1390.   * Probably left out some cases here -b.t.
  1391.   */
  1392.  
  1393.   if((target->level>SK_level(pl))
  1394.      || QUERY_FLAG(target,FLAG_UNDEAD)
  1395.      || QUERY_FLAG(target,FLAG_SPLITTING)
  1396.      || QUERY_FLAG(target,FLAG_HITBACK)) {
  1397.          pl->attacktype=AT_PHYSICAL;
  1398.          return damage;
  1399.   }
  1400.  
  1401.   damage += (((SK_level(pl)/18)+1)*pl->chosen_skill->stats.dam) + 1;
  1402.  
  1403.   return damage;
  1404. }
  1405.  
  1406. /* This is in the same spirit as the similar routine for spells 
  1407.  * it should be used anytime a function needs to check the user's
  1408.  * level.
  1409.  */
  1410.  
  1411. int SK_level(object *op) {  
  1412.   int level = op->level;
  1413.  
  1414. #ifdef ALLOW_SKILLS
  1415.   if(op->type==PLAYER && op->chosen_skill && op->chosen_skill->level!=0) {
  1416.     level = op->chosen_skill->level;
  1417.   }
  1418. #endif
  1419.   if(level<=0) level = 1;     /* safety */
  1420.  
  1421.   return level;
  1422. }
  1423.  
  1424. /* returns the amount of time it takes to use a skill. 
  1425.  * We allow for stats, and level to modify the amount 
  1426.  * of time. Monsters have no skill use time because of
  1427.  * the random nature in which use_monster_skill is called
  1428.  * already simulates this. -b.t. 
  1429.  */
  1430.  
  1431. int get_skill_time(object *op, int skillnr) {
  1432.   int time = skills[skillnr].time;
  1433.  
  1434.   if(!time || op->type!=PLAYER) 
  1435.      return 0;
  1436.   else {
  1437.     int sum = get_weighted_skill_stat_sum (op,skillnr);
  1438.     int level = SK_level(op)/10; 
  1439.  
  1440.     time *= 1/(1+(sum/15)+level);
  1441.   }
  1442.   return FABS(time);
  1443. }
  1444.  
  1445. /* get_skill_stat1() - returns the value of the primary skill
  1446.  * stat. Used in various skills code. -b.t.
  1447.  */
  1448.  
  1449. int get_skill_stat1(object *op) { 
  1450.   int stat_value = 0, stat=NO_STAT;
  1451.  
  1452.   if((op->chosen_skill) && ((stat = skills[op->chosen_skill->stats.sp].stat1)!=NO_STAT) )  
  1453.     stat_value = get_attr_value(&(op->stats),stat);
  1454.  
  1455.   return stat_value;
  1456. }
  1457.  
  1458. /* get_skill_stat2() - returns the value of the secondary skill
  1459.  * stat. Used in various skills code. -b.t.
  1460.  */
  1461.  
  1462. int get_skill_stat2(object *op) {
  1463.   int stat_value = 0,stat = NO_STAT;
  1464.  
  1465.   if((op->chosen_skill) && ((stat = skills[op->chosen_skill->stats.sp].stat2)!=NO_STAT) )  
  1466.     stat_value = get_attr_value(&(op->stats),stat);
  1467.  
  1468.   return stat_value;
  1469. }
  1470.  
  1471. /* get_skill_stat3() - returns the value of the tertiary skill 
  1472.  * stat. Used in various skills code. -b.t. 
  1473.  */ 
  1474.  
  1475. int get_skill_stat3(object *op) { 
  1476.   int stat_value = 0,stat = NO_STAT; 
  1477.  
  1478.   if((op->chosen_skill) && ((stat = skills[op->chosen_skill->stats.sp].stat3)!=NO_STAT) )
  1479.     stat_value = get_attr_value(&(op->stats),stat);  
  1480.  
  1481.   return stat_value; 
  1482.  
  1483. /*get_weighted_skill_stats() - */
  1484.  
  1485. int get_weighted_skill_stats(object *op) {
  1486.   int value=0;
  1487.  
  1488.   value = (get_skill_stat1(op)/2)+(get_skill_stat2(op)/4)+(get_skill_stat3(op)/4);
  1489.  
  1490.   return value;
  1491.  
  1492. }
  1493.  
  1494. /* find_skill() - looks for the skill and returns a pointer to it if found */
  1495.  
  1496. object *find_skill(object *op, int skillnr) {
  1497.   object *tmp;
  1498. #ifdef LINKED_SKILL_LIST
  1499.   objectlink *obl;
  1500.  
  1501.   if(op->sk_list!=NULL) {   /* use fast method */ 
  1502.      for(obl=op->sk_list;obl;obl=obl->next) 
  1503.          if(obl->id==skillnr&&obl->ob) return obl->ob;
  1504.   } 
  1505. #endif
  1506.  
  1507.   /* *sigh*; we didnt find it. Perhaps because the skill we requested
  1508.    * is an unapplied tool. Lets search entire inventory */
  1509.   for(tmp=op->inv;tmp;tmp=tmp->below) 
  1510.     if(tmp->type==SKILL&&tmp->stats.sp==skillnr) 
  1511.         return tmp;
  1512.  
  1513.   return ((object *) NULL); 
  1514. }
  1515.